package org.custom.spring.boot.mybatis.service.plugin.process.impl;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.custom.spring.boot.constant.MagicValue;
import org.custom.spring.boot.mybatis.service.plugin.process.MybatisProcessor;
import org.custom.spring.boot.mybatis.utils.MybatisStatementHandlerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDate;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * mybatis执行日志记录插件处理器
 *
 * @author 李坤
 * @date 2022/2/11 13:41
 */
public class ExecuteLoggerMybatisProcessor implements MybatisProcessor {
    private static final Logger logger = LoggerFactory.getLogger(ExecuteLoggerMybatisProcessor.class);

    private static final Map<String, StopWatch> WATCH = new ConcurrentHashMap<>(8);

    /**
     * 获取执行方法的全限定名的最后两个节点标识
     *
     * @param invocation 被代理对象
     *
     * @return 节点标识
     */
    private static String getMappedStatementLogo(Invocation invocation) {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        String label = MybatisStatementHandlerUtil.getBoundSqlLabel(SystemMetaObject.forObject(handler));

        String[] split = label.split(MagicValue.SPLIT_POINT);
        return split[split.length - 2] + MagicValue.POINT + split[split.length - 1];
    }

    /*--------------------------------------------------static methods------------------------------------------------*/

    /**
     * 获取当前线程创建的 StopWatch 对象
     *
     * @return StopWatch 对象
     */
    private static StopWatch getCurrentThreadStopWatch() {
        StopWatch stopWatch;
        final String thread = Thread.currentThread().getName();

        // 获取当前有效的 StopWatch 对象
        if (Objects.isNull(stopWatch = WATCH.get(thread))) {
            WATCH.put(thread, (stopWatch = new StopWatch()));
        } else if (Objects.nonNull(stopWatch.currentTaskName())) {
            stopWatch = new StopWatch();
        }
        return stopWatch;
    }

    @Override
    public Object apply(Invocation invocation, String finalSql) throws Throwable {
        // 计算并记录 SQL 执行时长
        StopWatch stopWatch = ExecuteLoggerMybatisProcessor.getCurrentThreadStopWatch();

        stopWatch.start(ExecuteLoggerMybatisProcessor.getMappedStatementLogo(invocation));
        Object proceed = invocation.proceed();
        stopWatch.stop();

        FileUtil.writeFile(finalSql, FileUtil.DEFAULT_PATH);
        // 注：此处记录的SQL执行时间为 SQL查询 及 其余后续插件前置代码执行 所花费的时间
        logger.info("原始SQL执行时长为：{} ms，原始SQL语句为：{}", stopWatch.getLastTaskTimeMillis(), finalSql);

        return proceed;
    }

    /*--------------------------------------------------static methods------------------------------------------------*/

    private static final class FileUtil {
        /**
         * 默认日志记录路径
         */
        public static final String DEFAULT_PATH = System.getProperty("user.home") + "\\technology\\";

        /**
         * 将给定数据写入到指定路径的文件中
         *
         * @param value    数据
         * @param filePath 文件路径
         *
         * @throws IOException IO异常
         */
        public static void writeFile(String value, String filePath) throws IOException {
            Path path;
            if (!Files.exists(path = Paths.get(filePath))) {
                Files.createDirectory(path);
            }
            FileUtil.writeFile(value, Paths.get(filePath + LocalDate.now() + ".sql"));
        }

        /**
         * 将给定的数据写入到给定路径的文件中
         *
         * @param value 待写入的数据
         * @param path  文件路径
         */
        public static void writeFile(String value, Path path) throws IOException {
            if (!Files.exists(path)) {
                Files.createFile(path);
            }
            Files.write(path, FileUtil.appendLineBreak(value).getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
        }

        /*---------------------------------------------private static methods---------------------------------------------*/

        /**
         * 为给定的数据添加换行符
         *
         * @param value 给定的数据
         *
         * @return 给定的数据
         */
        private static String appendLineBreak(String value) {
            return value + System.lineSeparator();
        }
    }
}
